<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>7.多级依赖注入设计 | 被删的前端游乐场</title>
    <meta name="generator" content="VuePress 1.8.2">
    
    <meta name="description" content="Just playing around">
    
    <link rel="preload" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css" as="style"><link rel="preload" href="/front-end-playground/assets/js/app.1e2670bf.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/2.38d016d1.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/3.e3f029cb.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/105.868b36a1.js" as="script">
    <link rel="stylesheet" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/front-end-playground/" class="home-link router-link-active"><!----> <span class="site-name">被删的前端游乐场</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link router-link-active">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link router-link-active">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-2b653b36><span class="stand" data-v-2b653b36></span> <div class="cat" data-v-2b653b36><div class="body" data-v-2b653b36></div> <div class="head" data-v-2b653b36><div class="ear" data-v-2b653b36></div> <div class="ear" data-v-2b653b36></div></div> <div class="face" data-v-2b653b36><div class="nose" data-v-2b653b36></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div></div> <div class="tail-container" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>Angular框架解读</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-0-prestart.html" class="sidebar-link">0.预热篇</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-metadata.html" class="sidebar-link">1.元数据和装饰器</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-dom-define.html" class="sidebar-link">2.视图抽象定义</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zonejs.html" class="sidebar-link">3.Zone区域之zone.js</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zone-ngzone.html" class="sidebar-link">4.Zone区域之ngZone</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-module.html" class="sidebar-link">5.模块化组织</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-1-basic-concepts.html" class="sidebar-link">6.依赖注入的基本概念</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.html" aria-current="page" class="active sidebar-link">7.多级依赖注入设计</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.html#模块注入器" class="sidebar-link">模块注入器</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.html#元素注入器" class="sidebar-link">元素注入器</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.html#angular-解析依赖过程" class="sidebar-link">Angular 解析依赖过程</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.html#总结" class="sidebar-link">总结</a></li></ul></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-3-bootstrap.html" class="sidebar-link">8.依赖注入的引导过程</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-ivy-0-design.html" class="sidebar-link">9.Ivy编译器整体设计</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-ivy-1-view-data-and-node-injector.html" class="sidebar-link">10.Ivy编译器的视图数据和依赖解析</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>玩转 Angular</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>试试 Cycle.js</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>D3.js 和 Tree</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>试试 Three.js</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>BOX2D.js 文档与教程</span> <span class="arrow right"></span></p> <!----></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><p>作为“为大型前端项目”而设计的前端框架，Angular 其实有许多值得参考和学习的设计，本系列主要用于研究这些设计和功能的实现原理。本文主要围绕 Angular 中的最大特点——依赖注入，介绍 Angular 中多级依赖注入的设计。</p> <p>上一篇我们介绍了 Angular 中的<code>Injectot</code>注入器、<code>Provider</code>提供者，以及注入器机制。那么，在 Angular 应用中，各个组件和模块间又是怎样共享依赖的，同样的服务是否可以多次实例化呢？</p> <p>组件和模块的依赖注入过程，离不开 Angular 多级依赖注入的设计，我们来看看。</p> <h1 id="多级依赖注入"><a href="#多级依赖注入" class="header-anchor">#</a> 多级依赖注入</h1> <p><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-1-basic-concepts.html">前面</a>我们说过，Angular 中的注入器是可继承、且分层的。</p> <p>在 Angular 中，有两个注入器层次结构：</p> <ul><li><code>ModuleInjector</code>模块注入器：使用<code>@NgModule()</code>或<code>@Injectable()</code>注解在此层次结构中配置<code>ModuleInjector</code></li> <li><code>ElementInjector</code>元素注入器：在每个 DOM 元素上隐式创建</li></ul> <p>模块注入器和元素注入器都是树状结构的，但它们的分层结构并不完全一致。</p> <h2 id="模块注入器"><a href="#模块注入器" class="header-anchor">#</a> 模块注入器</h2> <p>模块注入器的分层结构，除了与应用中模块设计有关系，还有平台模块（PlatformModule）注入器与应用程序模块（AppModule）注入器的分层结构。</p> <h3 id="平台模块-platformmodule-注入器"><a href="#平台模块-platformmodule-注入器" class="header-anchor">#</a> 平台模块（PlatformModule）注入器</h3> <p>在 Angular 术语中，平台是供 Angular 应用程序在其中运行的上下文。Angular 应用程序最常见的平台是 Web 浏览器，但它也可以是移动设备的操作系统或 Web 服务器。</p> <p>Angular 应用在启动时，会创建一个平台层：</p> <ul><li>平台是 Angular 在网页上的入口点，每个页面只有一个平台</li> <li>页面上运行的每个 Angular 应用程序，所共有的服务都在平台内绑定</li></ul> <p>一个 Angular 平台，主要包括创建模块实例、销毁等功能：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token decorator"><span class="token at operator">@</span><span class="token function">Injectable</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">PlatformRef</span> <span class="token punctuation">{</span>
  <span class="token comment">// 传入注入器，作为平台注入器</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> _injector<span class="token operator">:</span> Injector<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token comment">// 为给定的平台创建一个 @NgModule 的实例，以进行离线编译</span>
  <span class="token generic-function"><span class="token function">bootstrapModuleFactory</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">M</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>moduleFactory<span class="token operator">:</span> NgModuleFactory<span class="token operator">&lt;</span><span class="token constant">M</span><span class="token operator">&gt;</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> BootstrapOptions<span class="token punctuation">)</span><span class="token operator">:</span>
      <span class="token builtin">Promise</span><span class="token operator">&lt;</span>NgModuleRef<span class="token operator">&lt;</span><span class="token constant">M</span><span class="token operator">&gt;&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token comment">// 使用给定的运行时编译器，为给定的平台创建一个 @NgModule 的实例</span>
  <span class="token generic-function"><span class="token function">bootstrapModule</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">M</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>
      moduleType<span class="token operator">:</span> Type<span class="token operator">&lt;</span><span class="token constant">M</span><span class="token operator">&gt;</span><span class="token punctuation">,</span>
      compilerOptions<span class="token operator">:</span> <span class="token punctuation">(</span>CompilerOptions<span class="token operator">&amp;</span>BootstrapOptions<span class="token punctuation">)</span><span class="token operator">|</span>
      <span class="token builtin">Array</span><span class="token operator">&lt;</span>CompilerOptions<span class="token operator">&amp;</span>BootstrapOptions<span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>NgModuleRef<span class="token operator">&lt;</span><span class="token constant">M</span><span class="token operator">&gt;&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token comment">// 注册销毁平台时要调用的侦听器</span>
  <span class="token function">onDestroy</span><span class="token punctuation">(</span><span class="token function-variable function">callback</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token comment">// 获取平台注入器</span>
  <span class="token comment">// 该平台注入器是页面上每个 Angular 应用程序的父注入器，并提供单例提供程序</span>
  <span class="token keyword">get</span> <span class="token function">injector</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> Injector <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token comment">// 销毁页面上的当前 Angular 平台和所有 Angular 应用程序，包括销毁在平台上注册的所有模块和侦听器</span>
  <span class="token function">destroy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>实际上，平台在启动的时候(<code>bootstrapModuleFactory</code>方法中)，在<code>ngZone.run</code>中创建<code>ngZoneInjector</code>，以便在 Angular 区域中创建所有实例化的服务，而<code>ApplicationRef</code>（页面上运行的 Angular 应用程序）将在 Angular 区域之外创建。</p> <p>在浏览器中启动时，会创建浏览器平台：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">platformBrowser</span><span class="token operator">:</span> <span class="token punctuation">(</span>extraProviders<span class="token operator">?</span><span class="token operator">:</span> StaticProvider<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> PlatformRef <span class="token operator">=</span>
    <span class="token function">createPlatformFactory</span><span class="token punctuation">(</span>platformCore<span class="token punctuation">,</span> <span class="token string">'browser'</span><span class="token punctuation">,</span> <span class="token constant">INTERNAL_BROWSER_PLATFORM_PROVIDERS</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 其中，platformCore 平台必须包含在任何其他平台中</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> platformCore <span class="token operator">=</span> <span class="token function">createPlatformFactory</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token string">'core'</span><span class="token punctuation">,</span> _CORE_PLATFORM_PROVIDERS<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>使用平台工厂（例如上面的<code>createPlatformFactory</code>）创建平台时，将隐式初始化页面的平台：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">createPlatformFactory</span><span class="token punctuation">(</span>
    parentPlatformFactory<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>extraProviders<span class="token operator">?</span><span class="token operator">:</span> StaticProvider<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> PlatformRef<span class="token punctuation">)</span><span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">,</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    providers<span class="token operator">:</span> StaticProvider<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token punctuation">(</span>extraProviders<span class="token operator">?</span><span class="token operator">:</span> StaticProvider<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> PlatformRef <span class="token punctuation">{</span>
  <span class="token keyword">const</span> desc <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Platform: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> marker <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">InjectionToken</span><span class="token punctuation">(</span>desc<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// DI 令牌</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>extraProviders<span class="token operator">:</span> StaticProvider<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> platform <span class="token operator">=</span> <span class="token function">getPlatform</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 若平台已创建，则不做处理</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>platform <span class="token operator">||</span> platform<span class="token punctuation">.</span>injector<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token constant">ALLOW_MULTIPLE_PLATFORMS</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>parentPlatformFactory<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 若有父级平台，则直接使用父级平台，并更新相应的提供者</span>
        <span class="token function">parentPlatformFactory</span><span class="token punctuation">(</span>
            providers<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>extraProviders<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span><span class="token punctuation">{</span>provide<span class="token operator">:</span> marker<span class="token punctuation">,</span> useValue<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> injectedProviders<span class="token operator">:</span> StaticProvider<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span>
            providers<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>extraProviders<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span><span class="token punctuation">{</span>provide<span class="token operator">:</span> marker<span class="token punctuation">,</span> useValue<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
              provide<span class="token operator">:</span> <span class="token constant">INJECTOR_SCOPE</span><span class="token punctuation">,</span>
              useValue<span class="token operator">:</span> <span class="token string">'platform'</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">// 若无父级平台，则新建注入器，并创建平台</span>
        <span class="token function">createPlatform</span><span class="token punctuation">(</span>Injector<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">{</span>providers<span class="token operator">:</span> injectedProviders<span class="token punctuation">,</span> name<span class="token operator">:</span> desc<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token function">assertPlatform</span><span class="token punctuation">(</span>marker<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>通过以上过程，我们知道 Angular 应用在创建平台的时候，创建平台的模块注入器<code>ModuleInjector</code>。我们从<a href="">上一节</a><code>Injector</code>定义中也能看到，<code>NullInjector</code>是所有注入器的顶部：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Injector</span> <span class="token punctuation">{</span>
  <span class="token keyword">static</span> <span class="token constant">NULL</span><span class="token operator">:</span> Injector <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NullInjector</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>因此，在平台模块注入器之上，还有<code>NullInjector()</code>。而在平台模块注入器之下，则还有应用程序模块注入器。</p> <h3 id="应用程序根模块-appmodule-注入器"><a href="#应用程序根模块-appmodule-注入器" class="header-anchor">#</a> 应用程序根模块（AppModule）注入器</h3> <p>每个应用程序有至少一个 Angular 模块，根模块就是用来启动此应用的模块：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token decorator"><span class="token at operator">@</span><span class="token function">NgModule</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> providers<span class="token operator">:</span> <span class="token constant">APPLICATION_MODULE_PROVIDERS</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ApplicationModule</span> <span class="token punctuation">{</span>
  <span class="token comment">// ApplicationRef 需要引导程序提供组件</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>appRef<span class="token operator">:</span> ApplicationRef<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>AppModule</code>根应用模块由<code>BrowserModule</code>重新导出，当我们使用 CLI 的<code>new</code>命令创建新应用时，它会自动包含在根<code>AppModule</code>中。应用程序根模块中，提供者关联着内置的 DI 令牌，用于为引导程序配置根注入器。</p> <p>Angular 还将<code>ComponentFactoryResolver</code>添加到根模块注入器中。此解析器存储了<code>entryComponents</code>系列工厂，因此它负责动态创建组件。</p> <h3 id="模块注入器层级"><a href="#模块注入器层级" class="header-anchor">#</a> 模块注入器层级</h3> <p>到这里，我们可以简单地梳理出模块注入器的层级关系：</p> <ol><li>模块注入器树的最上层则是应用程序根模块（AppModule）注入器，称作 root。</li> <li>在 root 之上还有两个注入器，一个是平台模块（PlatformModule）注入器，一个是<code>NullInjector()</code>。</li></ol> <p>因此，模块注入器的分层结构如下：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/angular-1-injectors-1.svg" alt=""></p> <p>在我们实际的应用中，它很可能是这样的：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1_rjG7U4vLG_keRYoZnryxbA.png" alt=""></p> <p>Angular DI 具有分层注入体系，这意味着下级注入器也可以创建它们自己的服务实例。</p> <h2 id="元素注入器"><a href="#元素注入器" class="header-anchor">#</a> 元素注入器</h2> <p>前面说过，在 Angular 中有两个注入器层次结构，分别是模块注入器和元素注入器。</p> <h3 id="元素注入器的引入"><a href="#元素注入器的引入" class="header-anchor">#</a> 元素注入器的引入</h3> <p>当 Angular 中懒加载的模块开始广泛使用时，出现了一个 <a href="https://github.com/angular/angular/issues/13722" target="_blank" rel="noopener noreferrer">issue<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>：依赖注入系统导致懒加载模块的实例化加倍。</p> <p>在这一次修复中，引入了<a href="https://github.com/angular/angular/commit/13686bb" target="_blank" rel="noopener noreferrer">新的设计<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>：<strong>注入器使用两棵并行的树，一棵用于元素，另一棵用于模块</strong>。</p> <p>Angular 会为所有<code>entryComponents</code>创建宿主工厂，它们是所有其他组件的根视图。</p> <p>这意味着每次我们创建动态 Angular 组件时，都会使用根数据（<code>RootData</code>）创建根视图（<code>RootView</code>）：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">class</span> <span class="token class-name">ComponentFactory_</span> <span class="token keyword">extends</span> <span class="token class-name">ComponentFactory<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span></span><span class="token punctuation">{</span>
  <span class="token function">create</span><span class="token punctuation">(</span>
      injector<span class="token operator">:</span> Injector<span class="token punctuation">,</span> projectableNodes<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> rootSelectorOrNode<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token operator">|</span><span class="token builtin">any</span><span class="token punctuation">,</span>
      ngModule<span class="token operator">?</span><span class="token operator">:</span> NgModuleRef<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">)</span><span class="token operator">:</span> ComponentRef<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ngModule<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'ngModule should be provided'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">const</span> viewDef <span class="token operator">=</span> <span class="token function">resolveDefinition</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>viewDefFactory<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> componentNodeIndex <span class="token operator">=</span> viewDef<span class="token punctuation">.</span>nodes<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>element<span class="token operator">!</span><span class="token punctuation">.</span>componentProvider<span class="token operator">!</span><span class="token punctuation">.</span>nodeIndex<span class="token punctuation">;</span>
    <span class="token comment">// 使用根数据创建根视图</span>
    <span class="token keyword">const</span> view <span class="token operator">=</span> Services<span class="token punctuation">.</span><span class="token function">createRootView</span><span class="token punctuation">(</span>
        injector<span class="token punctuation">,</span> projectableNodes <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> rootSelectorOrNode<span class="token punctuation">,</span> viewDef<span class="token punctuation">,</span> ngModule<span class="token punctuation">,</span> <span class="token constant">EMPTY_CONTEXT</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// view.nodes 的访问器</span>
    <span class="token keyword">const</span> component <span class="token operator">=</span> <span class="token function">asProviderData</span><span class="token punctuation">(</span>view<span class="token punctuation">,</span> componentNodeIndex<span class="token punctuation">)</span><span class="token punctuation">.</span>instance<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>rootSelectorOrNode<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      view<span class="token punctuation">.</span>renderer<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token function">asElementData</span><span class="token punctuation">(</span>view<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>renderElement<span class="token punctuation">,</span> <span class="token string">'ng-version'</span><span class="token punctuation">,</span> <span class="token constant">VERSION</span><span class="token punctuation">.</span>full<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 创建组件</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ComponentRef_</span><span class="token punctuation">(</span>view<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">ViewRef_</span><span class="token punctuation">(</span>view<span class="token punctuation">)</span><span class="token punctuation">,</span> component<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>该根数据（<code>RootData</code>）包含对<code>elInjector</code>和<code>ngModule</code>注入器的引用：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">function</span> <span class="token function">createRootData</span><span class="token punctuation">(</span>
    elInjector<span class="token operator">:</span> Injector<span class="token punctuation">,</span> ngModule<span class="token operator">:</span> NgModuleRef<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">,</span> rendererFactory<span class="token operator">:</span> RendererFactory2<span class="token punctuation">,</span>
    projectableNodes<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> rootSelectorOrNode<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span><span class="token operator">:</span> RootData <span class="token punctuation">{</span>
  <span class="token keyword">const</span> sanitizer <span class="token operator">=</span> ngModule<span class="token punctuation">.</span>injector<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>Sanitizer<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> errorHandler <span class="token operator">=</span> ngModule<span class="token punctuation">.</span>injector<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>ErrorHandler<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> renderer <span class="token operator">=</span> rendererFactory<span class="token punctuation">.</span><span class="token function">createRenderer</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    ngModule<span class="token punctuation">,</span>
    injector<span class="token operator">:</span> elInjector<span class="token punctuation">,</span>
    projectableNodes<span class="token punctuation">,</span>
    selectorOrNode<span class="token operator">:</span> rootSelectorOrNode<span class="token punctuation">,</span>
    sanitizer<span class="token punctuation">,</span>
    rendererFactory<span class="token punctuation">,</span>
    renderer<span class="token punctuation">,</span>
    errorHandler<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>引入元素注入器树，原因是这样的设计比较简单。通过更改注入器层次结构，避免交错插入模块和组件注入器，从而导致延迟加载模块的双倍实例化。因为每个注入器都只有一个父对象，并且每次解析都必须精确地寻找一个注入器来检索依赖项。</p> <h3 id="元素注入器-element-injector"><a href="#元素注入器-element-injector" class="header-anchor">#</a> 元素注入器（Element Injector）</h3> <p>在 Angular 中，视图是模板的表示形式，它包含不同类型的节点，其中便有元素节点，元素注入器位于此节点上：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">ElementDef</span> <span class="token punctuation">{</span>
  <span class="token operator">...</span>
  <span class="token comment">// 在该视图中可见的 DI 的公共提供者</span>
  publicProviders<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">[</span>tokenKey<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">]</span><span class="token operator">:</span> NodeDef<span class="token punctuation">}</span><span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token comment">// 与 visiblePublicProviders 相同，但还包括位于此元素上的私有提供者</span>
  allProviders<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">[</span>tokenKey<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">]</span><span class="token operator">:</span> NodeDef<span class="token punctuation">}</span><span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>默认情况下<code>ElementInjector</code>为空，除非在<code>@Directive()</code>或<code>@Component()</code>的<code>providers</code>属性中进行配置。</p> <p>当 Angular 为嵌套的 HTML 元素创建元素注入器时，要么从父元素注入器继承它，要么直接将父元素注入器分配给子节点定义。</p> <p>如果子 HTML 元素上的元素注入器具有提供者，则应该继承该注入器。否则，无需为子组件创建单独的注入器，并且如果需要，可以直接从父级的注入器中解决依赖项。</p> <h3 id="元素注入器与模块注入器的设计"><a href="#元素注入器与模块注入器的设计" class="header-anchor">#</a> 元素注入器与模块注入器的设计</h3> <p>那么，元素注入器与模块注入器是从哪个地方开始成为平行树的呢？</p> <p>我们已经知道，应用程序根模块（<code>AppModule</code>）会在使用 CLI 的<code>new</code>命令创建新应用时，自动包含在根<code>AppModule</code>中。</p> <p>当应用程序（<code>ApplicationRef</code>）启动（<code>bootstrap</code>）时，会创建<code>entryComponent</code>：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">const</span> compRef <span class="token operator">=</span> componentFactory<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>Injector<span class="token punctuation">.</span><span class="token constant">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> selectorOrNode<span class="token punctuation">,</span> ngModule<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>该过程会使用根数据（<code>RootData</code>）创建根视图（<code>RootView</code>），同时会创建根元素注入器，在这里<code>elInjector</code>为<code>Injector.NULL</code>。</p> <p>在这里，Angular 的注入器树被分成元素注入器树和模块注入器树，这两个平行的树了。</p> <p>Angular 会有规律的创建下级注入器，每当 Angular 创建一个在<code>@Component()</code>中指定了<code>providers</code>的组件实例时，它也会为该实例创建一个新的子注入器。类似的，当在运行期间加载一个新的<code>NgModule</code>时，Angular 也可以为它创建一个拥有自己的提供者的注入器。</p> <p>子模块和组件注入器彼此独立，并且会为所提供的服务分别创建自己的实例。当 Angular 销毁<code>NgModule</code>或组件实例时，也会销毁这些注入器以及注入器中的那些服务实例。</p> <h2 id="angular-解析依赖过程"><a href="#angular-解析依赖过程" class="header-anchor">#</a> Angular 解析依赖过程</h2> <p>上面我们介绍了 Angular 中的两种注入器树：模块注入器树和元素注入器树。那么，Angular 在提供依赖时，又会以怎样的方式去进行解析呢。</p> <p>在 Angular 种，当为组件/指令解析 token 获取依赖时，Angular 分为两个阶段来解析它：</p> <ul><li>针对<code>ElementInjector</code>层次结构（其父级）</li> <li>针对<code>ModuleInjector</code>层次结构（其父级）</li></ul> <p>其过程如下（参考<a href="https://angular.cn/guide/hierarchical-dependency-injection#resolution-rules" target="_blank" rel="noopener noreferrer">多级注入器-解析规则<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>）：</p> <ol><li>当组件声明依赖项时，Angular 会尝试使用它自己的<code>ElementInjector</code>来满足该依赖。</li> <li>如果组件的注入器缺少提供者，它将把请求传给其父组件的<code>ElementInjector</code>。</li> <li>这些请求将继续转发，直到 Angular 找到可以处理该请求的注入器或用完祖先<code>ElementInjector</code>。</li> <li>如果 Angular 在任何<code>ElementInjector</code>中都找不到提供者，它将返回到发起请求的元素，并在<code>ModuleInjector</code>层次结构中进行查找。</li> <li>如果 Angular 仍然找不到提供者，它将引发错误。</li></ol> <p>为此，Angular 引入一种特殊的合并注入器。</p> <h3 id="合并注入器-merge-injector"><a href="#合并注入器-merge-injector" class="header-anchor">#</a> 合并注入器（Merge Injector）</h3> <p>合并注入器本身没有任何值，它只是视图和元素定义的组合。</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">class</span> <span class="token class-name">Injector_</span> <span class="token keyword">implements</span> <span class="token class-name">Injector</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> view<span class="token operator">:</span> ViewData<span class="token punctuation">,</span> <span class="token keyword">private</span> elDef<span class="token operator">:</span> NodeDef<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  <span class="token function">get</span><span class="token punctuation">(</span>token<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> notFoundValue<span class="token operator">:</span> <span class="token builtin">any</span> <span class="token operator">=</span> Injector<span class="token punctuation">.</span><span class="token constant">THROW_IF_NOT_FOUND</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">any</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> allowPrivateServices <span class="token operator">=</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>elDef <span class="token operator">?</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>elDef<span class="token punctuation">.</span>flags <span class="token operator">&amp;</span> NodeFlags<span class="token punctuation">.</span>ComponentView<span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> Services<span class="token punctuation">.</span><span class="token function">resolveDep</span><span class="token punctuation">(</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>view<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>elDef<span class="token punctuation">,</span> allowPrivateServices<span class="token punctuation">,</span>
        <span class="token punctuation">{</span>flags<span class="token operator">:</span> DepFlags<span class="token punctuation">.</span>None<span class="token punctuation">,</span> token<span class="token punctuation">,</span> tokenKey<span class="token operator">:</span> <span class="token function">tokenKey</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span> notFoundValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>当 Angular 解析依赖项时，合并注入器则是元素注入器树和模块注入器树之间的桥梁。当 Angular 尝试解析组件或指令中的某些依赖关系时，会使用合并注入器来遍历元素注入器树，然后，如果找不到依赖关系，则切换到模块注入器树以解决依赖关系。</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">class</span> <span class="token class-name">ViewContainerRef_</span> <span class="token keyword">implements</span> <span class="token class-name">ViewContainerData</span> <span class="token punctuation">{</span>
  <span class="token operator">...</span>
  <span class="token comment">// 父级试图元素注入器的查询</span>
  <span class="token keyword">get</span> <span class="token function">parentInjector</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> Injector <span class="token punctuation">{</span>
    <span class="token keyword">let</span> view <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_view<span class="token punctuation">;</span>
    <span class="token keyword">let</span> elDef <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_elDef<span class="token punctuation">.</span>parent<span class="token punctuation">;</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>elDef <span class="token operator">&amp;&amp;</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      elDef <span class="token operator">=</span> <span class="token function">viewParentEl</span><span class="token punctuation">(</span>view<span class="token punctuation">)</span><span class="token punctuation">;</span>
      view <span class="token operator">=</span> view<span class="token punctuation">.</span>parent<span class="token operator">!</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> view <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">Injector_</span><span class="token punctuation">(</span>view<span class="token punctuation">,</span> elDef<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Injector_</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_view<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><h3 id="解析过程"><a href="#解析过程" class="header-anchor">#</a> 解析过程</h3> <p>注入器是可继承的，这意味着如果指定的注入器无法解析某个依赖，它就会请求父注入器来解析它。具体的解析算法在<code>resolveDep()</code>方法中实现：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">resolveDep</span><span class="token punctuation">(</span>
    view<span class="token operator">:</span> ViewData<span class="token punctuation">,</span> elDef<span class="token operator">:</span> NodeDef<span class="token punctuation">,</span> allowPrivateServices<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">,</span> depDef<span class="token operator">:</span> DepDef<span class="token punctuation">,</span>
    notFoundValue<span class="token operator">:</span> <span class="token builtin">any</span> <span class="token operator">=</span> Injector<span class="token punctuation">.</span><span class="token constant">THROW_IF_NOT_FOUND</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">any</span> <span class="token punctuation">{</span>
  <span class="token comment">//</span>
  <span class="token comment">//          mod1</span>
  <span class="token comment">//         /</span>
  <span class="token comment">//       el1   mod2</span>
  <span class="token comment">//         \  /</span>
  <span class="token comment">//         el2</span>
  <span class="token comment">//</span>
  <span class="token comment">// 请求 el2.injector.get(token)时，按以下顺序检查并返回找到的第一个值：</span>
  <span class="token comment">// - el2.injector.get(token, default)</span>
  <span class="token comment">// - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -&gt; do not check the module</span>
  <span class="token comment">// - mod2.injector.get(token, default)</span>
<span class="token punctuation">}</span>
</code></pre></div><p>如果是<code>&lt;child&gt;&lt;/child&gt;</code>这样模板的根<code>AppComponent</code>组件，那么在 Angular 中将具有三个视图：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token comment">&lt;!-- HostView_AppComponent --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-app</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-app</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- View_AppComponent --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>child</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>child</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- View_ChildComponent --&gt;</span>
    some content
</code></pre></div><p>依赖解析过程，解析算法会基于视图层次结构，如图所示进行：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1_p3nTsvwXWjCilG5zG3ecKw.png" alt=""></p> <p>如果在子组件中解析某些令牌，Angular 将：</p> <ol><li>首先查看子元素注入器，进行检查<code>elRef.element.allProviders|publicProviders</code>。</li> <li>然后遍历所有父视图元素（1），并检查元素注入器中的提供者。</li> <li>如果下一个父视图元素等于<code>null</code>（2），则返回到<code>startView</code>（3），检查<code>startView.rootData.elnjector</code>（4）。</li> <li>只有在找不到令牌的情况下，才检查<code>startView.rootData module.injector</code>（ 5 ）。</li></ol> <p>由此可见，Angular 在遍历组件以解析某些依赖性时，将搜索特定视图的父元素而不是特定元素的父元素。视图的父元素可以通过以下方法获得：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token comment">// 对于组件视图，这是宿主元素</span>
<span class="token comment">// 对于嵌入式视图，这是包含视图容器的父节点的索引</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">viewParentEl</span><span class="token punctuation">(</span>view<span class="token operator">:</span> ViewData<span class="token punctuation">)</span><span class="token operator">:</span> NodeDef<span class="token operator">|</span><span class="token keyword">null</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> parentView <span class="token operator">=</span> view<span class="token punctuation">.</span>parent<span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>parentView<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> view<span class="token punctuation">.</span>parentNodeDef <span class="token operator">!</span><span class="token punctuation">.</span>parent<span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><h2 id="总结"><a href="#总结" class="header-anchor">#</a> 总结</h2> <p>本文主要介绍了 Angular 中注入器的层级结构，在 Angular 中有两棵平行的注入器树：模块注入器树和元素注入器树。</p> <p>元素注入器树的引入，主要是为了解决依赖注入解析懒加载模块时，导致模块的双倍实例化问题。在元素注入器树引入后，Angular 解析依赖的过程也有调整，优先寻找元素注入器以及父视图元素注入器等注入器的依赖，只有元素注入器中无法找到令牌时，才会查询模块注入器中的依赖。</p> <h3 id="参考"><a href="#参考" class="header-anchor">#</a> 参考</h3> <ul><li><a href="https://angular.cn/guide/hierarchical-dependency-injection" target="_blank" rel="noopener noreferrer">Angular-多级注入器<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://medium.com/angular-in-depth/angular-dependency-injection-and-tree-shakeable-tokens-4588a8f70d5d" target="_blank" rel="noopener noreferrer">What you always wanted to know about Angular Dependency Injection tree<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://indepth.dev/posts/1063/a-curious-case-of-the-host-decorator-and-element-injectors-in-angular" target="_blank" rel="noopener noreferrer">A curious case of the @Host decorator and Element Injectors in Angular<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/front-end-playground/edit/sourcecode/docs/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <!----> <blockquote>部分文章中使用了一些网站的截图，如果涉及侵权，请告诉我删一下谢谢~</blockquote> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本文版权归作者所有，欢迎转载，但未经作者同意必须保留此段声明，且在文章页面明显位置给出原文连接，否则保留追究法律责任的权利。</div> <div>出处：被删的前端游乐场</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-1-basic-concepts.html" class="prev">
          6.依赖注入的基本概念
        </a></span> <span class="next"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-3-bootstrap.html">
          8.依赖注入的引导过程
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-814" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><span class="el-popover__reference-wrapper"><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty1.svg);"></div></span></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/front-end-playground/assets/js/app.1e2670bf.js" defer></script><script src="/front-end-playground/assets/js/2.38d016d1.js" defer></script><script src="/front-end-playground/assets/js/3.e3f029cb.js" defer></script><script src="/front-end-playground/assets/js/105.868b36a1.js" defer></script>
  </body>
</html>
